module RbExt
  module Array
    
    # Splits an array by _separator_ and returns an array holding
    # the splitted values in arrays. Works like String#split for arrays.
    #
    # -- TODO: modify to support nested arrays
    #
    # Example:
    #  a = %w(a b c - d e f g - h i j - k)
    #  a.split("-")     # => [[a, b, c], [d, e, f, g], [h, i, j] [k]]
    #
    #  a = ["a", "b", ["c", "d", "-", "e"], "-", "f"]
    #  a.split("-")     # => [["a", "b", ["c", "d", "-", "e"]], ["f"]]
    #
    def split(separator)
      if self.count(separator) >= 1
        sep_indices = []
        result = []

        self.each_with_index do |item, index|
          sep_indices << index if item == separator
        end
        sep_indices << self.size

        start = 0
        sep_indices.each do |index|
          result << self.slice(start..index-1)
          start = index + 1
        end

        result
      else
        self
      end
    end


    # Computes each element of the array with _n_
    # and supports nested arrays.
    #
    # Example:
    #  a = ["a", "b", 1, "c", 2.5, ["d", 3], "e"]
    #  a.n_up(2)      # => ["aa", "bb", 2, "cc", 5.0, ["dd", 6], "ee"]
    #
    #  a3.n_up(3.5)   # => ["aaa", "bbb", 3.5, "ccc", 8.75, ["ddd", 10.5], "eee"]
    #
    def n_up(n)
      result = []

      self.map do |item|
        if item.respond_to?(:n_up)
          result << item.n_up(n)
        else
          result << item * n
        end
      end

      result
    end
    
    # Flattens first level of an array.
    #
    # Example:
    #   [1, [2, [3]]].flatten_once  # => [1, 2, [3]]
    #
    def flatten_top
      self.inject([]) do |acc, item|
        if item.is_a?(Array)
          acc.concat(item)
        else
          acc << item
        end
      end
    end
    
    
    
    ########################################################
    ################# TYPE MAPPING METHODS #################
    ########################################################
    
    # Returns a hash where its keys are the string versions
    # of the values in the array and invokes a block for each
    # element in the array taking the return value of the block
    # as the value for the hash element.
    #
    # Example:
    #  ["a", "b", "c"].to_hash_keys { |arr, e| arr.index(e) }   # => {"a"=>0, "b"=>1, "c"=>2}
    #  ["a", "b", "c", ["d", "e"]].to_hash_keys { |arr, e| arr.index(e) }   # => {"a"=>0, "b"=>1, "c"=>2, "[\"d\", \"e\"]"=>{"d"=>0, "e"=>1}}
    #
    def to_hash_keys(&block)
      out = {}
      
      self.collect do |item|
        if item.respond_to?(:to_hash_keys)
          out[item.to_s] = item.to_hash_keys(&block)
        else
          out[item.to_s] = block.call(self, item)
        end
      end
      
      out
    end
    
    # Returns a hash where its values are the string versions
    # of the values in the array and invokes a block for each
    # element in the array taking the return value of the block
    # as the key for the hash element.
    #
    # Example:
    #  ["a", "b", "c"].to_hash_values { |arr, e| arr.index(e) }   # => {"0"=>"a", "1"=>"b", "2"=>"c"}
    #  ["a", "b", "c", ["d", "e"]].to_hash_values { |arr, e| arr.index(e) }   # => {"0"=>"a", "1"=>"b", "2"=>"c", "{\"0\"=>\"d\", \"1\"=>\"e\"}"=>["d", "e"]}
    #
    def to_hash_values(&block)
      out = {}
      
      self.collect do |item|
        if item.respond_to?(:to_hash_values)
          out[item.to_hash_values(&block).to_s] = item
        else
          out[block.call(self, item).to_s] = item
        end
      end
      
      out
    end
    
    # Combines the array and the given array (or string)
    # using the values of this array as keys and the value
    # of the given array as values and returns all together
    # as a single hash.
    #
    # Example:
    #   %w(a b c).to_hash( %w(1 2 3) )    # => {"a"=>"1", "b"=>"2", "c"=>"3"}
    #   ["a", ["b", "c"]].to_hash( %w(1 2 3) )  # => {"a"=>"1", ["b", "c"]=>"2"}
    #   ["1", "2", "3"].to_hash(["a", ["b", "c"]])  # => {"1"=>"a", "2"=>["b", "c"], "3"=>nil}
    #   %w(a b c d).to_hash("hugo")   # => {"a"=>"h", "b"=>"u", "c"=>"g", "d"=>"o"}
    #
    def to_hash(values)
      Hash[ *(0...self.size()).inject([]) { |arr, ix| arr.push(self[ix], values[ix]) } ]
    end
    
    
    
    
    ########################################################
    ################## ARITHMETIC METHODS ##################
    ########################################################
    
    # Calculates the arithmetical mean of the values in the array
    # [http://en.wikipedia.org/wiki/Average#Arithmetic_mean]
    #
    # Example:
    #   [1, 2, 3, 4, 5].arithmetic_mean   # => 3.0
    #
    def arithmetic_mean
      inject(0) { |sum, val| sum + val.to_f } / length.to_f
    end
    alias_method :average, :arithmetic_mean
    alias_method :avg, :arithmetic_mean
    
    # Calculates the geographical mean of the values in the array
    # [http://en.wikipedia.org/wiki/Average#Geometric_mean]
    #
    # Example:
    #   [2, 3, 4.5].geometric_mean    # => 3.0
    #
    def geometric_mean
      inject(1) { |product, val| product * val.to_f } ** (1.0 / length.to_f)
    end
    
    # Calculates the harmonic mean of the values in the array
    # [http://en.wikipedia.org/wiki/Average#Harmonic_mean]
    #
    # Example:
    #   [1, 2, 3, 4, 5].harmonic_mean   # => 2.18978102189781
    #
    def harmonic_mean
      length.to_f / inject(0) { |sum, val| sum + (1.0 / val.to_f) }
    end
    
    # Calculates the root mean square of the values in the array
    # [http://en.wikipedia.org/wiki/Quadratic_mean]
    # 
    #
    # Example:
    #   [10, 12, 14, 20].quadratic_mean     # => 14.491376746189438
    #
    def quadratic_mean
      (inject(0) { |sum, val| sum + (val.to_f * val.to_f) } / length.to_f) ** (1.0 / 2.0)
    end
    
    # Calculates the mid range of the values in the array by (max + min) / 2.0
    # [http://en.wikipedia.org/wiki/Midrange]
    #
    # Example:
    #   [1, 5, 2, 3].mid_range    # => 3.0
    #
    def mid_range
      tmp = sort
      (tmp.last.to_f + tmp.first.to_f) / 2.0
    end
  end  
end